/*
 * Copyright 2010 Srikanth Reddy Lingala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.lingala.zip4j.crypto;

import java.util.Random;

import net.lingala.zip4j.crypto.engine.ZipCryptoEngine;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.util.InternalZipConstants;

public class StandardEncrypter implements IEncrypter {

    private ZipCryptoEngine zipCryptoEngine;
    private byte[] headerBytes;

    public StandardEncrypter(char[] password, int crc) throws ZipException {
        if (password == null || password.length <= 0) {
            throw new ZipException("input password is null or empty in standard encrpyter constructor");
        }

        this.zipCryptoEngine = new ZipCryptoEngine();

        this.headerBytes = new byte[InternalZipConstants.STD_DEC_HDR_SIZE];
        init(password, crc);
    }

    private void init(char[] password, int crc) throws ZipException {
        if (password == null || password.length <= 0) {
            throw new ZipException("input password is null or empty, cannot initialize standard encrypter");
        }
        zipCryptoEngine.initKeys(password);
        headerBytes = generateRandomBytes(InternalZipConstants.STD_DEC_HDR_SIZE);
        // Initialize again since the generated bytes were encrypted.
        zipCryptoEngine.initKeys(password);

        headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 1] = (byte) ((crc >>> 24));
        headerBytes[InternalZipConstants.STD_DEC_HDR_SIZE - 2] = (byte) ((crc >>> 16));

        if (headerBytes.length < InternalZipConstants.STD_DEC_HDR_SIZE) {
            throw new ZipException("invalid header bytes generated, cannot perform standard encryption");
        }

        encryptData(headerBytes);
    }

    public int encryptData(byte[] buff) throws ZipException {
        if (buff == null) {
            throw new NullPointerException();
        }
        return encryptData(buff, 0, buff.length);
    }

    public int encryptData(byte[] buff, int start, int len) throws ZipException {

        if (len < 0) {
            throw new ZipException("invalid length specified to decrpyt data");
        }

        try {
            for (int i = start; i < start + len; i++) {
                buff[i] = encryptByte(buff[i]);
            }
            return len;
        } catch (Exception e) {
            throw new ZipException(e);
        }
    }

    protected byte encryptByte(byte val) {
        byte temp_val = (byte) (val ^ zipCryptoEngine.decryptByte() & 0xff);
        zipCryptoEngine.updateKeys(val);
        return temp_val;
    }

    protected byte[] generateRandomBytes(int size) throws ZipException {

        if (size <= 0) {
            throw new ZipException("size is either 0 or less than 0, cannot generate header for standard encryptor");
        }

        byte[] buff = new byte[size];

        Random rand = new Random();

        for (int i = 0; i < buff.length; i++) {
            // Encrypted to get less predictability for poorly implemented
            // rand functions.
            buff[i] = encryptByte((byte) rand.nextInt(256));
        }

//		buff[0] = (byte)87;
//		buff[1] = (byte)176;
//		buff[2] = (byte)-49;
//		buff[3] = (byte)-43;
//		buff[4] = (byte)93;
//		buff[5] = (byte)-204;
//		buff[6] = (byte)-105;
//		buff[7] = (byte)213;
//		buff[8] = (byte)-80;
//		buff[9] = (byte)-8;
//		buff[10] = (byte)21;
//		buff[11] = (byte)242;

//		for( int j=0; j<2; j++ ) {
//			Random rand = new Random();
//			int i = rand.nextInt();
//			buff[0+j*4] = (byte)(i>>24);
//			buff[1+j*4] = (byte)(i>>16);
//			buff[2+j*4] = (byte)(i>>8);
//			buff[3+j*4] = (byte)i;
//		}
        return buff;
    }

    public byte[] getHeaderBytes() {
        return headerBytes;
    }

}
